#include "LuaTimer.h"
#include "Globals.h"
#include <string.h>

extern ObjPool<LuaTimer> *g_pLuaTimerPool;
extern struct event_base *g_pEventBase;

LuaTimer::LuaTimer()
{
    memset(this, 0, sizeof(*this));
    m_nPoolID = -1;
    m_nTimerID = -1;
    CleanUp();
    g_pTimerIDManager->Add(this);
}


LuaTimer::~LuaTimer()
{
    g_pTimerIDManager->Del(this);
    m_nTimerID = -1;
    Stop();
    CleanUp();
}

void LuaTimer::CleanUp()
{
    m_nMaxTimes = 0;
    m_nCurTimes = 0;
    m_pBase = NULL;
    memset(&m_oTimerEvent, 0, sizeof(m_oTimerEvent));
    memset(&m_oTimeVal, 0, sizeof(m_oTimeVal));
    m_bIsEnabled = false;
    m_bIsTimerPersist = false;
    m_bReadyToStop = false;

    m_pL = NULL;
    m_nObjID = -1;
    m_nEvent = 0;

    m_nParam1 = 0;
    m_nParam2 = 0;
    m_nParam3 = 0;
}

void LuaTimer::SetEvent(lua_State *pL, int nObjID, int nEvent)
{
    m_pL = pL;
    m_nObjID = nObjID;
    m_nEvent = nEvent;
}

void LuaTimer::SetParam(int nParam1, int nParam2, int nParam3)
{
    m_nParam1 = nParam1;
    m_nParam2 = nParam2;
    m_nParam3 = nParam3;
}

bool LuaTimer::Start(struct event_base* pBase, int nInterval, int nTimes)
{
    if(!pBase){
        fprintf(stderr, "LuaTimer::Start, pBase is null\n");
        return false;
    }
    if (m_bIsEnabled)
    {
        printf("LuaTimer::Start, timer[%d] already started\n", m_nEvent);
        return false;
    }

    m_pBase = pBase;
    if (nTimes <= 0){
        m_bIsTimerPersist = true;
        event_assign(&m_oTimerEvent, pBase, -1, EV_PERSIST, TimerCallBack, this);
    }
    else{
        m_bIsTimerPersist = false;
        event_assign(&m_oTimerEvent, pBase, -1, 0, TimerCallBack, this);
    }

    m_nMaxTimes = nTimes;
    m_nCurTimes = 0;
    evutil_timerclear(&m_oTimeVal);
    m_oTimeVal.tv_sec = nInterval/1000;
    m_oTimeVal.tv_usec = (nInterval%1000) * 1000;
    if (event_add(&m_oTimerEvent, &m_oTimeVal) == 0){
        m_bIsEnabled = true;
        return true;
    }
    else{
        return false;
    }
}


void LuaTimer::ReadyToStop()
{
    m_bReadyToStop = true;
}

bool LuaTimer::Stop()
{
    if (!m_bIsEnabled)
        return false;

    if (m_bReadyToStop == false && m_nObjID != -1) 
    {
        Obj* pObj = g_pObjIDManager->Get(m_nObjID);
        assert(pObj);
        if (pObj) pObj->DelTimerID(GetID());
    }
    
    if (m_bIsTimerPersist && m_pBase){
        if (event_del(&m_oTimerEvent) != 0){
            fprintf(stderr, "event_del fail\n");
        }
        m_bIsTimerPersist = false;
    }

    if (m_nPoolID != -1 && g_pLuaTimerPool != NULL){
        g_pLuaTimerPool->DeleteObj(this);
    }
    else{
        fprintf(stderr, "Stop timer failed\n");
        return false;
    }

    m_bIsEnabled = false;
    return true;
}

void LuaTimer::TimerCallBack(evutil_socket_t nFD, short nEvents, void* pArgs)
{

    LuaTimer* pTimer = (LuaTimer*)pArgs;
    if (!pTimer->m_bIsEnabled || !pTimer->m_pL)
        return;

    pTimer->IncTimes();

    if (pTimer->m_bReadyToStop){
        pTimer->Stop();
        return;
    }

    lua_rawgeti(pTimer->m_pL, LUA_REGISTRYINDEX, g_nTimerDispatcherRef);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nTimerID);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nObjID);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nEvent);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nParam1);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nParam2);
    lua_pushnumber(pTimer->m_pL, pTimer->m_nParam3);

    CHECK_DEAL_TIME(1000, "file = %s, line = %d, timerId = %d, objId = %d, event = %d", __FILE__, __LINE__, pTimer->m_nTimerID, pTimer->m_nObjID, pTimer->m_nEvent);
    if (lua_pcall(pTimer->m_pL, 6, 1, g_nErrorFuncIndex) != 0){
        fprintf(stderr, "call TimerDispatch failed:%s\n", lua_tostring(pTimer->m_pL, -1));
        lua_pop(pTimer->m_pL, 1);
        return; 
    }

    bool ret = (bool)lua_toboolean(pTimer->m_pL, -1);
    lua_pop(pTimer->m_pL, 1);
    if (!ret){
        pTimer->Stop();
        return;
    }
    
    if (!pTimer->m_bIsTimerPersist && pTimer->m_nCurTimes < pTimer->m_nMaxTimes){
        event_add(&pTimer->m_oTimerEvent, &pTimer->m_oTimeVal);
    }

    if (!pTimer->m_bIsTimerPersist && pTimer->m_nCurTimes >= pTimer->m_nMaxTimes){
        pTimer->Stop();
    }
}

bool LuaTimer::IncTimes()
{
    if (m_nCurTimes > m_nMaxTimes){
        return false;
    }
    else{
        m_nCurTimes++;
        return true;
    }
}

//Call from lua: _AddTimer(nObjID, nEvent, nInterval, nMaxTimes, nParam1, nParam2, nParam3)
static int AddTimer(lua_State* pL)
{
    int nObjID = (int)lua_tonumber(pL, 1);
    int nEvent = (int)lua_tonumber(pL, 2);
    int nInterval = (int)lua_tonumber(pL, 3);
    int nMaxTimes = (int)lua_tonumber(pL, 4);
    int nParam1 = (int)lua_tonumber(pL, 5);
    int nParam2 = (int)lua_tonumber(pL, 6);
    int nParam3 = (int)lua_tonumber(pL, 7);

    LuaTimer *pTimer = g_pLuaTimerPool->NewObj();
    if (!pTimer){
        printf("[ERROR] g_pLuaTimerPool->NewObj()\n");
        lua_pushnumber(pL, -1);
        return 1;
    }

    pTimer->SetEvent(pL, nObjID, nEvent);
    pTimer->SetParam(nParam1, nParam2, nParam3);
    if (pTimer->Start(g_pEventBase, nInterval, nMaxTimes)){
        if (nObjID != -1) 
        {
            Obj* pObj = g_pObjIDManager->Get(nObjID);
            assert(pObj);
            if (pObj) pObj->AddTimerID(pTimer->GetID());
        }

        lua_pushnumber(pL, pTimer->GetID());
        return 1;
    }

    lua_pushnumber(pL, -1);
    return 1;
}

//Call from lua: _DelTimer(nTimerID)
static int DelTimer(lua_State* pL)
{
    int nTimerID = (int)lua_tonumber(pL, 1);
    int nObjID = (int)lua_tonumber(pL, 2);

    LuaTimer *pTimer = g_pTimerIDManager->Get(nTimerID);
    if (pTimer && pTimer->GetInputObjID() == nObjID)
    {
        if (nObjID != -1) 
        {
            Obj* pObj = g_pObjIDManager->Get(nObjID);
            assert(pObj);
            if (pObj) pObj->DelTimerID(pTimer->GetID());
        }

        pTimer->ReadyToStop();
    }
    return 0;
}


void RegisterTimer(lua_State* pL)
{
    lua_pushcfunction(pL, AddTimer);
    lua_setglobal(pL, "_AddTimer");

    lua_pushcfunction(pL, DelTimer);
    lua_setglobal(pL, "_DelTimer");
}

